// Copyright (c) 2024 刻BITTER
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.


#pragma once

/**
 * @brief   包含了用于AVR 单片机的通用引脚定义和操作，依赖Arduino 库
 */

#include <Arduino.h>

#include "ioxx_common.hpp"
#include "ioxx_include_device.hpp"

#if defined(IOXX_CONFIG_ARDUINO_PIN_HAS_POINTER_MEMBER) && (IOXX_CONFIG_ARDUINO_PIN_HAS_POINTER_MEMBER == 1)

    #define _IOXX__ENABLE__ARDUINO__PIN__HAS__POINTER  // 只在头文件内部使用的宏，所以采用繁琐的命名

#endif

namespace ioxx {

    class Pin {
       public:
        using PortType = volatile uint8_t *;

       private:
#ifdef _IOXX__ENABLE__ARDUINO__PIN__HAS__POINTER
        PortType _port_out;
        PortType _port_in;
#endif
        uint8_t _pin_num;
        uint8_t _pin_mask;

       public:
        /**
         * @brief 从引脚编号获取引脚寄存器信息
         */
        Pin(uint8_t pin_num) {
            _pin_num = pin_num;
            _pin_mask = digitalPinToBitMask(pin_num);

#ifdef _IOXX__ENABLE__ARDUINO__PIN__HAS__POINTER

            uint8_t port_num = digitalPinToPort(pin_num);
            _port_out = portOutputRegister(port_num);
            _port_in = portInputRegister(port_num);
#endif
        }

        uint8_t port() const {
            return digitalPinToPort(_pin_num);
        }

        PortType port_out() const {
#ifdef _IOXX__ENABLE__ARDUINO__PIN__HAS__POINTER

            return _port_out;

#else

            uint8_t port_num = digitalPinToPort(pin_num);
            return portOutputRegister(port_num);

#endif
        }

        PortType port_in() const {
#ifdef _IOXX__ENABLE__ARDUINO__PIN__HAS__POINTER

            return _port_in;

#else

            uint8_t port_num = digitalPinToPort(pin_num);
            return portInputRegister(port_num);

#endif
        }

        uint8_t pin_mask() const {
            return _pin_mask;
        }


        void clr() const {
            *port_out() &= ~_pin_mask;
        }


        void set() const {
            *port_out() |= pin_mask();
        }

        void set(bool level) const {
            if (level) {
                set();
            }
            else {
                clr();
            }
        }

        auto get() const {
            return *port_in() & pin_mask();
        }

        void toggle() const {
            *port_out() ^= pin_mask();
        }


        void mode_input() const {
            pinMode(_pin_num, INPUT);
        }


        void mode_input_pullup() const {
            pinMode(_pin_num, INPUT_PULLUP);
        }


        void mode_output() const {
            pinMode(_pin_num, OUTPUT);
        }


        /**
         * @brief 可以隐式将 Pin 对象反向转换回引脚编号
         *
         * @return uint8_t
         */
        operator uint8_t() const {
            return _pin_num;
        }
    };


    inline void setpin(Pin p) {
        p.set();
    }


    inline void clrpin(Pin p) {
        p.clr();
    }


    inline auto getpin(Pin p) {
        return p.get();
    }


    inline void toggle_pin(Pin p) {
        p.toggle();
    }


    enum class pull {
        none = 0,
        up = 0xff,


        down = 0,  // #TODO avr 单片机没有下拉功能
    };


    /**
     * @brief 用于将引脚在初始的模式和输入模式间切换
     *
     * 软件I2C 等库代码需要让一个引脚分时工作在输入 / 输出模式，
     * 假定用户预先将引脚配置为输出，且输出模式、速度等已经配置好，
     * 可以用InputScope 临时将引脚置为输入模式，读取输入后再恢复为原输出模式
     */
    class InputPinScope {
       public:
        using PinType = Pin;

       private:
        volatile uint8_t *_port_ddr;
        volatile uint8_t *_port_out;
        uint8_t _initial_mode;
        uint8_t _initial_pull;
        uint8_t _pin_mask;

       public:
        InputPinScope(Pin pin, pull pull_mode) {
            uint8_t r = pin.port();
            uint8_t _pin_mask = pin.pin_mask();

            volatile uint8_t *_port_out = pin.port_out();
            volatile uint8_t *_port_ddr = portModeRegister(r);

            _initial_mode = *_port_ddr & _pin_mask;
            _initial_pull = *_port_out & _pin_mask;

            *_port_ddr &= ~_pin_mask;
            *_port_out |= (_pin_mask & static_cast<uint8_t>(pull_mode));
        }

        InputPinScope(Pin pin) :
            InputPinScope(pin, pull::up) {}

        ~InputPinScope() {
            uint8_t tmp;
            tmp = *_port_out;
            tmp &= ~_pin_mask;
            tmp |= _initial_pull;
            *_port_out = tmp;

            *_port_ddr |= _initial_mode;
        }
    };


    /**
     * @brief InputPinScope 的简化版，默认引脚初始状态为输出模式，固定将引脚上拉
     *
     */
    class ReadPinScope {
       public:
        using PinType = Pin;

       private:
        volatile uint8_t *_port_ddr;
        volatile uint8_t *_port_out;
        uint8_t _initial_pull;
        uint8_t _pin_mask;

       public:
        ReadPinScope(Pin pin) {
            uint8_t r = pin.port();
            uint8_t _pin_mask = pin.pin_mask();

            volatile uint8_t *_port_out = pin.port_out();
            volatile uint8_t *_port_ddr = portModeRegister(r);

            _initial_pull = *_port_out & _pin_mask;

            *_port_ddr &= ~_pin_mask;
            *_port_out |= _pin_mask;
        }

        ~ReadPinScope() {
            *_port_out &= ~_pin_mask;
            *_port_ddr |= _pin_mask;  // 直接将引脚置为输出，忽略初始状态
        }
    };


}  // namespace ioxx